package org.budo.time.impl;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import org.budo.time.Time;
import org.budo.time.TimeFormat;
import org.budo.time.TimePoint;
import org.budo.time.TimeSpan;

import java.io.Serializable;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;

/**
 * 表示一个时间点
 *
 * @author limingwei2
 */
@Getter
@Setter
@Slf4j
@NoArgsConstructor // 默认构造器 序列化需要
public class AbstractTimePoint implements TimePoint, Serializable {
    private static final long serialVersionUID = -4618733947086651402L;

    private Date _date;

    public AbstractTimePoint(Date date) {
        this._date = date;
    }

    @Override
    public Date getDate() {
        return this._date;
    }

    protected TimePoint calendarFieldMinus(Integer field, Number amount) {
        if (null == amount) {
            return this;
        }

        return this.timeFieldPlus(field, -amount.intValue());
    }

    @Override
    public Date toDate() {
        if (null == this.getDate()) {
            return null;
        }

        return new Date(this.toMilliSeconds());
    }

    @Override
    public Calendar toCalendar() {
        if (null == this.getDate()) {
            return null;
        }

        Calendar calendar = Calendar.getInstance();
        calendar.setTime(this.getDate());
        return calendar;
    }

    @Override
    public Timestamp toTimestamp() {
        if (null == this.getDate()) {
            return null;
        }

        return new Timestamp(this.toMilliSeconds());
    }

    @Override
    public java.sql.Time toSqlTime() {
        if (null == this.getDate()) {
            return null;
        }

        return new java.sql.Time(this.toMilliSeconds());
    }

    @Override
    public java.sql.Date toSqlDate() {
        if (null == this.getDate()) {
            return null;
        }

        return new java.sql.Date(this.toMilliSeconds());
    }

    @Override
    public LocalDate toLocalDate() {
        if (null == this.getDate()) {
            return null;
        }

        Integer year = this.getYear();
        int month = this.getMonth() + 1; // 月份 +1
        Integer day = this.getDay();

        return LocalDate.of(year, month, day);
    }

    @Override
    public LocalTime toLocalTime() {
        if (null == this.getDate()) {
            return null;
        }

        Integer hour = this.getHour();
        Integer minute = this.getMinute();
        Integer second = this.getSecond();
        int nanoOfSecond = this.getMilliSecond() * 1000;

        return LocalTime.of(hour, minute, second, nanoOfSecond);
    }

    @Override
    public Instant toInstant() {
        if (null == this.getDate()) {
            return null;
        }

        return Instant.ofEpochMilli(this.toMilliSeconds());
    }

    @Override
    public LocalDateTime toLocalDateTime() {
        if (null == this.getDate()) {
            return null;
        }

        return LocalDateTime.ofInstant(this.toInstant(), ZoneId.systemDefault());
    }

    /**
     * Returns the number of milliseconds since January 1, 1970, 00:00:00 GMT
     */
    @Override
    public Long toMilliSeconds() {
        if (null == this.getDate()) {
            return null;
        }

        return this.getDate().getTime();
    }

    /**
     * Returns the number of seconds since 1970-1-1, 00:00:00 GMT
     */
    @Override
    public Long toSeconds() {
        if (null == this.getDate()) {
            return null;
        }

        return this.toMilliSeconds() / 1000;
    }

    @Override
    public TimePoint setYear(Number year) {
        return this.timeFieldSet(Calendar.YEAR, year);
    }

    /**
     * @param month 一月是0 二月是1
     */
    @Override
    public TimePoint setMonth(Number month) {
        return this.timeFieldSet(Calendar.MONTH, month);
    }

    /**
     * DAY_OF_MONTH
     */
    @Override
    public TimePoint setDay(Number day) {
        return this.timeFieldSet(Calendar.DAY_OF_MONTH, day);
    }

    @Override
    public TimePoint setDayOfWeek(Number dayOfWeek) {
        return this.timeFieldSet(Calendar.DAY_OF_WEEK, dayOfWeek);
    }

    @Override
    public TimePoint setWeekOfMonth(Number weekOfMonth) {
        return this.timeFieldSet(Calendar.WEEK_OF_MONTH, weekOfMonth);
    }

    @Override
    public TimePoint setHour(Number hour) {
        return this.timeFieldSet(Calendar.HOUR_OF_DAY, hour);
    }

    @Override
    public TimePoint setMinute(Number minute) {
        return this.timeFieldSet(Calendar.MINUTE, minute);
    }

    @Override
    public TimePoint setSecond(Number second) {
        return this.timeFieldSet(Calendar.SECOND, second);
    }

    @Override
    public TimePoint setMilliSecond(Number milliSecond) {
        return this.timeFieldSet(Calendar.MILLISECOND, milliSecond);
    }

    @Override
    public TimePoint withYear(Number year) {
        return this.setYear(year);
    }

    @Override
    public TimePoint withMonth(Number month) {
        return this.setMonth(month);
    }

    @Override
    public TimePoint withDay(Number day) {
        return this.setDay(day);
    }

    @Override
    public TimePoint withHour(Number hour) {
        return this.setHour(hour);
    }

    @Override
    public TimePoint withMinute(Number minute) {
        return this.setMinute(minute);
    }

    @Override
    public TimePoint withSecond(Number second) {
        return this.setSecond(second);
    }

    @Override
    public TimePoint withMilliSecond(Number milliSecond) {
        return this.setMilliSecond(milliSecond);
    }

    /**
     * 更新日期为本月最后一天
     */
    @Override
    public TimePoint setDayMax() {
        return this.setDay(this.getDayMax());
    }

    /**
     * 更新时间为 23, 59, 59, 999
     */
    @Override
    public TimePoint setTimeMax() {
        return this.setTime(23, 59, 59, 999);
    }

    /**
     * 将时间部分设置为 0:0:0.0 后返回
     */
    @Override
    public TimePoint datePart() {
        return this.setTime(0, 0, 0, 0);
    }

    /**
     * DAY_OF_MONTH 返回当月最大日期 30/31/29/28
     */
    @Override
    public Integer getDayMax() {
        return this.toCalendar().getActualMaximum(Calendar.DAY_OF_MONTH);
    }

    @Override
    public Integer getYear() {
        if (null == this.getDate()) {
            return null;
        }

        return this.toCalendar().get(Calendar.YEAR);
    }

    @Override
    public Integer getMonth() {
        if (null == this.getDate()) {
            return null;
        }

        return this.toCalendar().get(Calendar.MONTH);
    }

    /**
     * DAY_OF_MONTH
     */
    @Override
    public Integer getDay() {
        if (null == this.getDate()) {
            return null;
        }

        return this.toCalendar().get(Calendar.DAY_OF_MONTH);
    }

    @Override
    public Integer getHour() {
        if (null == this.getDate()) {
            return null;
        }

        return this.toCalendar().get(Calendar.HOUR_OF_DAY);
    }

    @Override
    public Integer getMinute() {
        if (null == this.getDate()) {
            return null;
        }

        return this.toCalendar().get(Calendar.MINUTE);
    }

    @Override
    public Integer getSecond() {
        if (null == this.getDate()) {
            return null;
        }

        return this.toCalendar().get(Calendar.SECOND);
    }

    @Override
    public Integer getMilliSecond() {
        if (null == this.getDate()) {
            return null;
        }

        return this.toCalendar().get(Calendar.MILLISECOND);
    }

    @Override
    public Integer getDayOfWeek() {
        if (null == this.getDate()) {
            return null;
        }

        return this.toCalendar().get(Calendar.DAY_OF_WEEK);
    }

    @Override
    public Integer getWeekOfMonth() {
        if (null == this.getDate()) {
            return null;
        }

        return this.toCalendar().get(Calendar.WEEK_OF_MONTH);
    }

    // 加法
    @Override
    public TimePoint plusYear(Number year) {
        return this.timeFieldPlus(Calendar.YEAR, year);
    }

    @Override
    public TimePoint plusMonth(Number month) {
        return this.timeFieldPlus(Calendar.MONTH, month);
    }

    /**
     * DAY_OF_MONTH
     */
    @Override
    public TimePoint plusDay(Number day) {
        return this.timeFieldPlus(Calendar.DAY_OF_MONTH, day);
    }

    @Override
    public TimePoint plusHour(Number hour) {
        return this.timeFieldPlus(Calendar.HOUR_OF_DAY, hour);
    }

    @Override
    public TimePoint plusMinute(Number minute) {
        return this.timeFieldPlus(Calendar.MINUTE, minute);
    }

    @Override
    public TimePoint plusSecond(Number second) {
        return this.timeFieldPlus(Calendar.SECOND, second);
    }

    @Override
    public TimePoint plusMilliSecond(Number milliSecond) {
        return this.timeFieldPlus(Calendar.MILLISECOND, milliSecond);
    }

    // 减法
    @Override
    public TimePoint minusYear(Number year) {
        return this.calendarFieldMinus(Calendar.YEAR, year);
    }

    @Override
    public TimePoint minusMonth(Number month) {
        return this.calendarFieldMinus(Calendar.MONTH, month);
    }

    /**
     * DAY_OF_MONTH
     */
    @Override
    public TimePoint minusDay(Number day) {
        return this.calendarFieldMinus(Calendar.DAY_OF_MONTH, day);
    }

    @Override
    public TimePoint minusHour(Number hour) {
        return this.calendarFieldMinus(Calendar.HOUR_OF_DAY, hour);
    }

    @Override
    public TimePoint minusMinute(Number minute) {
        return this.calendarFieldMinus(Calendar.MINUTE, minute);
    }

    @Override
    public TimePoint minusSecond(Number second) {
        return this.calendarFieldMinus(Calendar.SECOND, second);
    }

    @Override
    public TimePoint minusMilliSecond(Number milliSecond) {
        return this.calendarFieldMinus(Calendar.MILLISECOND, milliSecond);
    }

    @Override
    public TimePoint sharp() {
        return this.setMinute(0) //
                .setSecond(0) //
                .setMilliSecond(0);
    }

    /**
     * 设置时间部分
     */
    @Override
    public TimePoint setTime(Number hour, Number minute, Number second, Number milliSecond) {
        return this.setHour(hour) //
                .setMinute(minute) //
                .setSecond(second) //
                .setMilliSecond(milliSecond);
    }

    @Override
    public TimePoint setTime(Number hour, Number minute, Number second) {
        return this.setHour(hour) //
                .setMinute(minute) //
                .setSecond(second);
    }

    /**
     * 设置日期部分
     *
     * @param year
     * @param month 一月是0，二月是1
     * @param day
     */
    @Override
    public TimePoint setDate(Number year, Number month, Number day) {
        return this.setYear(year).setMonth(month).setDay(day);
    }

    /**
     * 将日期部分设置为 1970-0-1，即 70年1月1号 后返回
     */
    @Override
    public TimePoint timePart() {
        return this.setDate(1970, 0, 1);
    }

    /**
     * 是周六
     */
    @Override
    public Boolean isSaturday() {
        if (null == this.getDate()) {
            return null;
        }

        return this.getDayOfWeek().intValue() == Calendar.SATURDAY;
    }

    /**
     * 是周日
     */
    @Override
    public Boolean isSunday() {
        if (null == this.getDate()) {
            return null;
        }

        return this.getDayOfWeek().intValue() == Calendar.SUNDAY;
    }

    // 时间比较
    /**
     * 二者中有空值时返回false
     */
    @Override
    public Boolean isBefore(TimePoint that) {
        if (null == this.getDate() || null == that || null == that.getDate()) {
            return false;
        }

        return this.toMilliSeconds() < that.toMilliSeconds();
    }

    /**
     * 二者中有空值时返回false
     */
    @Override
    public Boolean isNotBefore(TimePoint that) {
        if (null == this.getDate() || null == that || null == that.getDate()) {
            return false;
        }

        return !this.isBefore(that);
    }

    /**
     * 二者中有空值时返回false
     */
    @Override
    public Boolean isAfter(TimePoint that) {
        if (null == this.getDate() || null == that || null == that.getDate()) {
            return false;
        }

        return this.toMilliSeconds() > that.toMilliSeconds();
    }

    @Override
    public Boolean isAfter(Date that) {
        return this.isAfter(Time.when(that));
    }

    /**
     * 二者中有空值时返回false
     */
    @Override
    public Boolean isNotAfter(TimePoint that) {
        if (null == this.getDate() || null == that || null == that.getDate()) {
            return false;
        }

        return !this.isAfter(that);
    }

    /**
     * 二者中有空值时返回false
     */
    @Override
    public Boolean isEqual(TimePoint that) {
        if (null == this.getDate() || null == that || null == that.getDate()) {
            log.error("#527 isEqual, this=" + this + ", that=" + that);

            return false;
        }

        return this.toMilliSeconds().longValue() == that.toMilliSeconds().longValue();
    }

    /**
     * 二者中有空值时返回false
     */
    @Override
    public Boolean isNotEqual(TimePoint that) {
        if (null == this.getDate() || null == that || null == that.getDate()) {
            return false;
        }

        return !this.isEqual(that);
    }

    /**
     * 三者中有空值时返回false 与两端相等返回true
     */
    @Override
    public Boolean isBetween(TimePoint min, TimePoint max) {
        if (null == this.getDate() || null == min || null == min.getDate() || null == max || null == max.getDate()) {
            return false;
        }

        return this.isNotBefore(min) && this.isNotAfter(max);
    }

    /**
     * 三者中有空值时返回false 与两端相等返回false
     */
    @Override
    public Boolean isNotBetween(TimePoint min, TimePoint max) {
        if (null == this.getDate() || null == min || null == min.getDate() || null == max || null == max.getDate()) {
            return false;
        }

        return !this.isBetween(min, max);
    }

    /**
     * 三者中有空值时返回false 与两端相等返回false
     */
    @Override
    public Boolean isBetwixt(TimePoint min, TimePoint max) {
        if (null == this.getDate() || null == min || null == min.getDate() || null == max || null == max.getDate()) {
            return false;
        }

        return this.isAfter(min) && this.isBefore(max);
    }

    /**
     * 三者中有空值时返回false 与两端相等返回true
     */
    @Override
    public Boolean isNotBetwixt(TimePoint min, TimePoint max) {
        if (null == this.getDate() || null == min || null == min.getDate() || null == max || null == max.getDate()) {
            return false;
        }

        return !this.isBetwixt(min, max);
    }

    // 时间范围工具
    /**
     * @param that 应该是偏后的时间,否则会会返回负数
     */
    @Override
    public TimeSpan until(TimePoint that) {
        return new BudoTimeSpan(this, that);
    }

    /**
     * @param that 应该是偏后的时间,否则会会返回负数
     */
    @Override
    public TimeSpan until(Date that) {
        return this.until(Time.when(that));
    }

    @Override
    public TimeSpan between(Long that) {
        return this.betwixt(that).abs();
    }

    /**
     * @param that 一定会返回正数
     */
    @Override
    public TimeSpan between(TimePoint that) {
        return this.betwixt(that).abs();
    }

    /**
     * @param that 一定会返回正数
     */
    @Override
    public TimeSpan between(Date that) {
        return this.between(Time.when(that));
    }

    @Override
    public TimeSpan betwixt(TimePoint that) {
        return new BudoTimeSpan(this, that);
    }

    @Override
    public TimeSpan betwixt(Date that) {
        return this.betwixt(Time.when(that));
    }

    @Override
    public TimeSpan betwixt(Long that) {
        return this.betwixt(Time.when(that));
    }

    // 格式化工具
    @Override
    public String toString(DateFormat dateFormat) {
        if (null == this.getDate()) {
            return null;
        }

        synchronized (dateFormat) {
            return dateFormat.format(this.getDate());
        }
    }

    @Override
    public String toString(TimeFormat timeFormat) {
        return timeFormat.format(this.getDate());
    }

    /**
     * yyyy-MM-dd
     */
    @Override
    public String toString(String pattern) {
        DateFormatFactory dateFormatFactory = DateFormatFactory.getCurrent();
        DateFormat dateFormat = dateFormatFactory.getDateFormat(pattern);
        return this.toString(dateFormat);
    }

    @Override
    public String toString(String pattern, Locale locale) {
        DateFormatFactory dateFormatFactory = DateFormatFactory.getCurrent();
        DateFormat dateFormat = dateFormatFactory.getDateFormat(pattern, locale);
        return this.toString(dateFormat);
    }

    @Override
    public Boolean isBetween(Date min, Date max) {
        return this.isBetween(Time.when(min), Time.when(max));
    }

    @Override
    public Boolean isBetwixt(Date min, Date max) {
        return this.isBetwixt(Time.when(min), Time.when(max));
    }

    @Override
    public Boolean isBefore(Date that) {
        return this.isBefore(Time.when(that));
    }

    @Override
    public Boolean isEqual(Date that) {
        return this.isEqual(Time.when(that));
    }

    @Override
    public Boolean isBeforeNow() {
        return this.isBefore(Time.now());
    }

    @Override
    public TimePoint startOfMonth() {
        return this.setDay(1).setTime(0, 0, 0, 0);
    }

    @Override
    public TimePoint startOfWeek() {
        return this.minusDay(this.getDayOfWeek()).setTime(0, 0, 0, 0);
    }

    @Override
    public TimePoint startOfYear() {
        return this.setMonth(0).setDay(1).setTime(0, 0, 0, 0);
    }

    @Override
    public TimePoint startOfDay() {
        return this.setTime(0, 0, 0, 0);
    }

    @Override
    public TimePoint startOfHour() {
        return this.setMinute(0).setSecond(0).setMilliSecond(0);
    }

    @Override
    public TimePoint ceilSecond() {
        if (this.getSecond() > 0) {
            return this.plusMinute(1).setSecond(0);
        }

        return this;
    }

    @Override
    public TimePoint roundSecond() {
        if (this.getSecond() > 29) {
            return this.plusMinute(1).setSecond(0);
        }

        return this;
    }

    @Override
    public TimePoint floorSecond() {
        return this.setSecond(0);
    }

    @Override
    public TimePoint ceilMilliSecond() {
        if (this.getMilliSecond() > 0) {
            return this.plusSecond(1).setMilliSecond(0);
        }

        return this;
    }

    @Override
    public TimePoint roundMilliSecond() {
        if (this.getMilliSecond() > 499) {
            return this.plusSecond(1).setMilliSecond(0);
        }

        return this;
    }

    @Override
    public TimePoint floorMilliSecond() {
        return this.setMilliSecond(0);
    }

    @Override
    public TimeSpan betweenNow() {
        return this.between(Time.now());
    }

    @Override
    public <T> T toType(Class<T> type) {
        if (null == type) {
            throw new IllegalArgumentException("#771 type=" + type + ", this=" + this);
        }

        if (Timestamp.class.equals(type)) {
            return (T) this.toTimestamp();
        }

        if (Date.class.equals(type)) {
            return (T) this.toDate();
        }

        throw new IllegalArgumentException("#774 type=" + type + ", this=" + this);
    }

    @Override
    public TimePoint or(TimePoint defaultValue) {
        if (null != this.getDate()) {
            return this;
        }

        return defaultValue;
    }

    @Override
    public TimePoint orNow() {
        return this.or(Time.now());
    }

    @Override
    public TimePoint timeFieldSet(Integer field, Number value) {
        throw new RuntimeException();
    }

    @Override
    public TimePoint timeFieldPlus(Integer field, Number amount) {
        throw new RuntimeException();
    }

    public String toString() {
        return super.toString() + ", " + this.toString("yyyy-MM-dd HH:mm:ss.SSS");
    }
}
